package com.luych.toolbox.gateway.center.controller;

import com.luych.toolbox.common.feign.intf.gateway.center.FileFeign;
import com.luych.toolbox.common.feign.view.gateway.center.FileView;
import io.swagger.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Api(tags = "文件管理")
@RestController
@RequestMapping("/api/file")
public class FileController implements FileFeign {

    private static final Logger logger = LoggerFactory.getLogger(FileController.class);

    private final Map<String, Long> expires = new ConcurrentHashMap<>();

    @Value("${toolbox.base.file.path}")
    private String filePath;
    @Value("${toolbox.base.file.expire:60000}")
    private long expire;

    @Override
    public String post(MultipartFile multipartFile, String name) {
        if (StringUtils.isEmpty(name)) {
            name = UUID.randomUUID().toString();
        }
        try {
            logger.info("start write file {}, size {} ...", name, multipartFile.getSize());
            multipartFile.transferTo(new File(filePath + File.separator + name));
            logger.info("complete write file {}, size {} .", name, multipartFile.getSize());
        } catch (IOException e) {
            throw new RuntimeException("Failed to transfer the file", e);
        }
        expires.put(name, System.currentTimeMillis() + expire);
        return name;
    }

    @Override
    public Resource get(String uuid) {
        File file = new File(filePath + File.separator + uuid);
        return new FileSystemResource(file);
    }

    @Override
    public void put(String uuid, MultipartFile multipartFile) {
        File file = new File(filePath + File.separator + uuid);
        if (file.exists() && file.isFile()) {
            if (!file.delete()) {
                throw new RuntimeException("Failed to delete the file");
            }
        }
        try {
            logger.info("start write file {}, size {} ...", uuid, multipartFile.getSize());
            multipartFile.transferTo(file);
            logger.info("complete write file {}, size {} .", uuid, multipartFile.getSize());
        } catch (IOException e) {
            throw new RuntimeException("Failed to transfer the file", e);
        }
        expires.put(uuid, System.currentTimeMillis() + expire);
    }

    @Override
    public void delete(String uuid) {
        File file = new File(filePath + File.separator + uuid);
        if (!file.delete()) {
            throw new RuntimeException("Failed to delete the file");
        }
        expires.remove(uuid);
    }

    @Override
    public void confirm(String uuid) {
        expires.remove(uuid);
    }

    @ApiOperation("下载一个文件")
    @ApiResponses({@ApiResponse(code = 200, message = "获取成功，返回文件二进制流")})
    @GetMapping("/{uuid}/download")
    public ResponseEntity<Resource> download(@ApiParam(value = "文件UUID") @PathVariable String uuid) throws IOException {
        File file = new File(filePath + File.separator + uuid);
        if (!file.exists()) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        Resource resource = new FileSystemResource(file);
        ContentDisposition disposition = ContentDisposition.builder("attachment")
                .filename(uuid, StandardCharsets.UTF_8).build();
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .header(HttpHeaders.CONTENT_DISPOSITION, disposition.toString())
                .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength()))
                .body(resource);
    }

    @ApiOperation("上传一个文件")
    @ApiResponses({@ApiResponse(code = 200, message = "上传成功，返回文件UUID")})
    @PostMapping("/{uuid}/upload")
    public ResponseEntity<String> upload(@ApiParam(value = "文件UUID") @PathVariable String uuid,
                                         HttpServletRequest request) throws IOException {
        ServletInputStream requestInputStream = request.getInputStream();
        try (FileOutputStream fileOutputStream = new FileOutputStream(filePath + File.separator + uuid)) {
            logger.info("start write file {}, size {} ...", uuid, requestInputStream.available());
            int length = StreamUtils.copy(requestInputStream, fileOutputStream);
            logger.info("complete write file {}, size {} .", uuid, length);
        }
        expires.put(uuid, System.currentTimeMillis() + expire);
        return ResponseEntity.ok().body(uuid);
    }

    @Scheduled(fixedRate = 1000L)
    public void expire() {
        for (Map.Entry<String, Long> entry : expires.entrySet()) {
            if (entry.getValue() < System.currentTimeMillis()) {
                logger.info("File {} is expired", entry.getKey());
                this.delete(entry.getKey());
            }
        }
    }

    @Override
    public List<FileView> list() {
        File[] files = new File(filePath).listFiles();
        if (files == null) {
            return new ArrayList<>();
        }
        List<FileView> views = new ArrayList<>();
        for (File file : files) {
            FileView view = new FileView();
            view.setUuid(file.getName());
            view.setCreateAt(new Date(file.lastModified()));
            if (expires.containsKey(file.getName())) {
                view.setConfirmed(false);
                view.setExpireAt(new Date(expires.get(file.getName())));
            } else {
                view.setConfirmed(true);
            }
            views.add(view);
        }
        return views;
    }
}
